/**
* Auteur : Dominique GUERIN
* dominiquephDOTguerinATgmailDOT..
* dominiqueDOTguerinATinseeDOTfr(ance)  
* Remerciements: � Keith Brown pour ses deux livres sur la s�curit� sous Windows et ses diff�rents articles
*                 et � Michel Barnett de Microsoft pour ses deux articles 
*                ".NET Remoting Authentication and Authorization Sample"  et le code qui les accompagne
*  Le code est utilisable, modifiable et redistribuable � volont� sous la condition de ne pas supprimer ces 7 lignes.
*/ 
package fr.doume.tcp.authenticator;


//tomcat 5.5
//import org.apache.commons.logging.Log;
//import org.apache.commons.logging.LogFactory;

//tomcat 6
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;

import java.net.InetAddress;
import fr.doume.tcp.sspi.SSPException;
import javax.servlet.ServletContext;

import javax.net.ssl.SSLContext;
import java.io.FileInputStream;
import java.security.KeyStore;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.security.GeneralSecurityException;

/**
* The instance of this class is a singleton. It is given to the instance of AutyhenticationContext.
* <c>SSPAuthentication</c> and <c>TranslationRolesIntoSids</c>  
* <li>Read the file <code>webapps/<application>/META-INF/context.xml</code> of the application.
* <pre>
*     &lt;!-- By default 127.0.0.1 . Can be an IP address or the DNS name of the server.
*     &lt;Parameter name="serveraddress" value="127.0.0.1" override="false"/>
*     -->
*    This parameter is REQUIRED when tomcat is running on UNIX.
*    Must be the DNS name when we use SSL between and the Windows service
*    With SSL, Tomcat has to known  the DNS name of the address or the DNS Name of the Windows server
*    &lt;!--
*    &lt;Parameter name="serveraddress" value="negoserver.test.net" override="false"/>
*    -->
*     &lt;!-- By default 21000. 
*     &lt;Parameter name="port" value="21000" override="false"/>
*     -->
*     &lt;!-- By default, not used. This parameter is not recommended
*     &lt;Parameter name="onlyntlm" value="" override="false"/>
*     -->
*     &lt;!-- This two parameters as required only when SSL is used between Tomcat and the Windows service.
*     &lt;Parameter name="truststorepath" value="C:\Users\minou\Downloads\makecert\trust_w543118a.jks" override="false" />
*     &lt;Parameter name="truststorepassword" value="changeit" override="false" />
*     -->
*     &lt;!-- Time life in ms of the translation of the names of roles into Sids: By Default without limit --> 
*     &lt;!--
*     &lt;Parameter name="timelifemaprolesintosids" value="-1" override="false" />
*     -->
*     &lt;!-- By default, not used
*     &lt;Parameter name="nogroupsinad" value="" override="false" />
*     -->
*     &lt;!-- By default, not used
*     &lt;Parameter name="usernamewithoutdomainasprefix" value="" override="false" />
*     -->
*     &lt;!-- By default, not used
*     &lt;Parameter name="loginauthenticationwithoutad" value="" override="false" />
*     -->
*     &lt;!-- By default, not used. You can add a commonrole, when you are not interested by the roles. 
*     &lt;Parameter name="commonrole" value="commonrole" override="false" />
*     -->
*     &lt;!-- By default, not used. If you cannot be authenticated, the browser can send a popup to choose another account. 
*     &lt;Parameter name="choiceoftheaccount" value="" override="false" />
*     -->
*     &lt;!-- By default, not used
*     &lt;Parameter name="realmsandwindowsgroups" value="" override="false" />
*     -->
* </pre>
*/
public class Parameters
{
  
  private static Log log = LogFactory.getLog(Parameters.class);
  
  String windowsServer;
  int port;
  InetAddress address_server;
  String truststoreName;
  String trustpassword;
  SSLSocketFactory sslfactory = null;
  String onlyntlm;
  public String NEGOTIATE = "Negotiate";
  int timelifeTranslation;
  boolean aregroupsinad = true;
  boolean aregroupsinrealm = false;
  boolean clientNameWithoutDomainName = false;
  boolean isloginwithoutad = false;
  String commonrole = null;
  boolean ischoiceoftheaccount = false;
  boolean isauthenticationboundtothetcpconnection = false;
  boolean mustcreatesession = true;
  boolean isonlykerberos = false;
  int timeoutntlm = 17;
  boolean ischoicebetwweenspnegoandntlm = false; 


  public Parameters( ServletContext srvctx )
  {
    
    String serverport = srvctx.getInitParameter("port");
    truststoreName = srvctx.getInitParameter("truststorepath");
    trustpassword = srvctx.getInitParameter("truststorepassword");
    windowsServer = srvctx.getInitParameter("serveraddress");
    onlyntlm = srvctx.getInitParameter("onlyntlm");
    String timelifemaprolesintosids = srvctx.getInitParameter("timelifemaprolesintosids");
    String nogroupsinad = srvctx.getInitParameter("nogroupsinad"); 
    String groupsinad = srvctx.getInitParameter("groupsinad"); 
    String usernamewithoutdomainasprefix = srvctx.getInitParameter("usernamewithoutdomainasprefix");
/**/    String usernamewithdomainasprefix = srvctx.getInitParameter("usernamewithdomainasprefix");
    String loginauthenticationwithoutad = srvctx.getInitParameter("loginauthenticationwithoutad");
/**/    String loginauthenticationwithad = srvctx.getInitParameter("loginauthenticationwithad");
    commonrole = srvctx.getInitParameter("commonrole");
    String choiceoftheaccount = srvctx.getInitParameter("choiceoftheaccount");
    String realmsandwindowsgroups = srvctx.getInitParameter("realmsandwindowsgroups");
/**/
    String bindauthenticationtotcpconnection = srvctx.getInitParameter("bindauthenticationtotcpconnection");
    String simultaneousauthentications = srvctx.getInitParameter("simultaneousauthentications");
    String nocreatesessionafterauthentication = srvctx.getInitParameter("nocreatesessionafterauthentication");
    String onlykerberos = srvctx.getInitParameter("onlykerberos");
    String timeoutntlmauthentication = srvctx.getInitParameter("timeoutntlmauthentication");
    String spnegoandntlm = srvctx.getInitParameter("spnegoandntlm");


    if (log.isDebugEnabled())
    {
      log.debug(" serveraddress: PARAMETER : " + windowsServer);
      log.debug(" port: PARAMETER : " + serverport );
      log.debug(" truststorepath: PARAMETER : " + truststoreName);
      log.debug(" truststorepassword: PARAMETER : " + trustpassword);
      log.debug(" onlyntlm: PARAMETER : " + onlyntlm);
      log.debug(" timelifemaprolesintosids: PARAMETER : " + timelifemaprolesintosids);
      log.debug(" nogroupsinad: PARAMETER : " + nogroupsinad);
      log.debug(" groupsinad: PARAMETER : " + groupsinad);
      log.debug(" usernamewithoutdomainasprefix: PARAMETER : " + usernamewithoutdomainasprefix);
      log.debug(" usernamewithdomainasprefix: PARAMETER : " + usernamewithdomainasprefix);
      log.debug(" loginauthenticationwithoutad: PARAMETER : " + loginauthenticationwithoutad);
      log.debug(" loginauthenticationwithad: PARAMETER : " + loginauthenticationwithad);
      log.debug(" commonrole : PARAMETER : " + commonrole);
      log.debug(" choiceoftheaccount : PARAMETER: " + choiceoftheaccount);
      log.debug(" realmsandwindowsgroups : PARAMETER : " + realmsandwindowsgroups);
      log.debug(" bindauthenticationtotcpconnection : PARAMETER: " + bindauthenticationtotcpconnection);
      log.debug(" simultaneousauthentications : PARAMETER: " + simultaneousauthentications);
      log.debug(" nocreatesessionafterauthentication : PARAMETER: " + nocreatesessionafterauthentication);
      log.debug(" timeoutntlmauthentication : PARAMETER: " + timeoutntlmauthentication);
      log.debug(" onlykerberos : PARAMETER: " + onlykerberos);
      log.debug(" spnegoandntlm : PARAMETER: " + spnegoandntlm);

    }
    
    if ( windowsServer == null )
    {
      windowsServer = "localhost";
      if (log.isDebugEnabled())  log.debug("Parameter servername is not present: => localhost ");
    }
    
    try
    {
      port = Integer.parseInt(serverport);
    }
    catch( java.lang.NumberFormatException e)
    {
      port = 0;
    }
    
    if (port <= 0 || port > Short.MAX_VALUE )
    {
      if (log.isDebugEnabled()) log.debug("Error in the parameters:  A port TCP must be > 0 and  < 65536");
      if (log.isDebugEnabled())  log.debug("Parameter port is not present or incoherent: => port = 21000");  
      port = 21000;
    }

    if (truststoreName != null)
    {
      if (log.isDebugEnabled()) log.debug("truststoreName is not null");
    }

    try
    {
      timelifeTranslation = Integer.parseInt(timelifemaprolesintosids);
    }
    catch( java.lang.NumberFormatException e)
    {
      timelifeTranslation = -2;
    }
    
    if (timelifeTranslation <= 0  )
    {
      if (log.isDebugEnabled()) log.debug("Error in the parameters:  timelifeTranslation >0 or equals -1");
      if (log.isDebugEnabled())  log.debug("Parameter timelifeTranslation is not present or incoherent: => timelifeTranslation = -1");  
      timelifeTranslation = -1;
    }
    
    if (onlyntlm != null)
    {
      if (log.isDebugEnabled()) log.debug("onlyntlm is not null");
      NEGOTIATE = "NTLM";
    }

    if (spnegoandntlm != null)
    {
      if (log.isDebugEnabled()) log.debug("spnegoandntlm is not null");
      ischoicebetwweenspnegoandntlm = true;
    }

    if (groupsinad != null )
    {
      if (log.isDebugEnabled()) log.debug("groupsinad is not null");
      aregroupsinad = true;
      aregroupsinrealm = false;
    }

    if (nogroupsinad != null )
    {
      if (log.isDebugEnabled()) log.debug("nogroupsinad is not null");
      aregroupsinad = false;
      aregroupsinrealm = true;
    }
 
    if (usernamewithdomainasprefix != null )
    {
      if (log.isDebugEnabled()) log.debug("usernamewithdomainasprefix is not null");
      clientNameWithoutDomainName = false;
    }    

    if (usernamewithoutdomainasprefix != null )
    {
      if (log.isDebugEnabled()) log.debug("usernamewithoutdomainasprefix is not null");
      clientNameWithoutDomainName = true;
    }

    if (loginauthenticationwithad != null)
    {
      if (log.isDebugEnabled()) log.debug("loginauthenticationwithad is not null");
      isloginwithoutad = false;
    }

    if (loginauthenticationwithoutad != null)
    {
      if (log.isDebugEnabled()) log.debug("loginauthenticationwithoutad is not null");
      isloginwithoutad = true;
    }
    
    if (commonrole != null)
    {
      if (log.isDebugEnabled()) log.debug("commonrole is not null");
      if (commonrole.length() == 0){
        commonrole = "commonrole";
        if (log.isDebugEnabled()) log.debug("commonrole is defined but without value => commonrole = \"commonrole\"");
      }  
    }

    if (choiceoftheaccount != null)
    {
      if (log.isDebugEnabled()) log.debug("choiceoftheaccount is not null");
      ischoiceoftheaccount = true;
    }

    if (realmsandwindowsgroups != null)
    {
      aregroupsinad =true;
      aregroupsinrealm = true;
    }        

    if ( bindauthenticationtotcpconnection != null || simultaneousauthentications != null)
    {
      isauthenticationboundtothetcpconnection = true;
    }
    
    if (nocreatesessionafterauthentication != null)
    {
      mustcreatesession = false;
    }

    if (onlykerberos != null)
    {
      isonlykerberos = true;
    }
    
    try
    {
      timeoutntlm = Integer.parseInt(timeoutntlmauthentication);
    }
    catch( java.lang.NumberFormatException e)
    {
      timeoutntlm = 17;
    }
    if ( timeoutntlm <=16 || timeoutntlm > 120 )
    {
      timeoutntlm = 17;
    }
    
  }

  /**
  * SSL is used iIf and only if the parameter <code>truststorename</code> is not void
  * In this case, the parameter <code>serveraddress</code> must be the DNS name (or dns alias) 
  * of the windows server wher NegosServer is running. So, the java code can verify the certificate.<br/>
  */
  public boolean isSSLRequired()
  {
    return (truststoreName != null);
  }
  
  /**
  * Provides the password associated with the file containing the certificates of Certification Authorities 
  * that have certified the certificate of the Windows server.
  * This setting is only useful when using SSL between Tomcat and NegoServer.<br/>
  */ 
  private String getTrustPassword()
  {
    return trustpassword;
  }

  /**
  * <code>initSSL</code> is called only if we use SSL between Tomcat and Negoserver <br/>
  */
  private SSLSocketFactory initSSL()
  throws SSPException
  {
    if (truststoreName == null) return null;
    try
    {
      String trustpasswd = getTrustPassword();
      if (trustpasswd == null) throw new SSPException("Exception avec SSL: Keystore password is unknown");
      char[] pwd = trustpasswd .toCharArray();
      
      SSLContext ctx = SSLContext.getInstance("TLS");
      KeyStore trust = KeyStore.getInstance("JKS");
      
      FileInputStream trustf = new FileInputStream(truststoreName);
      trust.load(trustf, pwd );
      
      TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
      tmf.init(trust);
      
      ctx.init(null, tmf.getTrustManagers(), null );
      sslfactory = ctx.getSocketFactory();
    }
    catch(IOException e)
    {
      throw new SSPException("Exception with SSL: truststore is not found " + e.getMessage());
    }
    catch(GeneralSecurityException e)
    {
      throw new SSPException("Exception with SSL: TLS or Key Management " + e.getMessage());
    }
    return sslfactory;
  }

    /** 
    * Returns the address or the name of the server where Negoserver is running. 
    * This information is given in the parameters.<br/>
    */      
  public String getWindowsServerName()
  {
    return windowsServer;
  }
    
    /** 
    * Address of the windows server where is running Negoserver. By default, <code>localhost</code>.<br/>
    */
  public InetAddress getInetAddressServer()
  throws SSPException
  {
    try
    {
      address_server = InetAddress.getByName(windowsServer);
    }
    catch(java.net.UnknownHostException e)
    {
      if (log.isDebugEnabled())  log.debug("Parameter servername is not coherent:" + e.getMessage() + " => localhost  ");
      try
      {
        address_server = InetAddress.getByName("localhost");
      }
      catch(Exception internalEx)
      {
        throw new SSPException("Irrecouvrable error in the parameters : "+  internalEx.getMessage());
      }
    }
    return address_server;
  }
  /**
  * Returns the TCP port. By default 21000<br/>
  */ 
  public int getPort()
  {
    return port;
  }
  
  /**
  * Returns null or the SSL Factory used by SSPAuthentication when SSL is used.<br/>
  */
  public SSLSocketFactory getSSLFactory()
  throws SSPException
  {
    if (truststoreName == null) return null;
    
    if (sslfactory != null)
    {
      return sslfactory;
    }
    else
    {
      return initSSL();
    }
  }

  /**
  * When <code>onlyntlm</code> is set in the parameters, NTLM replaces Negotiate in the headers. SPNEGO is no more used.<br/>
  */
  
  public boolean isNTLMonly()
  {
    return (onlyntlm != null);
  }
  
  /**
  * Sets the maximum lifetime of a translation from roles into SIDS. By default, a negative duration the value is negative. thus
  * the timelife is infinite.<br/>
  */ 
  public int getTimeLifeMappingRolesIntoSids()
  {
    return timelifeTranslation;
  }
  
    /**
  * When <code>nogroupsinad</code> is set in the parameters, there are no more translations of the tomcat roles into SIDs.<br/>
  */
  public boolean areGroupsInAd()
  {
    return aregroupsinad;
  }
  
  public boolean areGroupsInRealm()
  {
    return aregroupsinrealm;
  }
  
  public boolean isUserNameWithoutDomainName()
  {
    return clientNameWithoutDomainName;
  }
  
  public boolean isLoginWithoutAD()
  {
    return isloginwithoutad;
  }

  public boolean isLoginWithRealm()
  {
    return isloginwithoutad;
  }

  public boolean isLoginWithAd()
  {
    return !isloginwithoutad;
  }
  
  public boolean isWithCommonRole()
  {
    return (commonrole != null);
  }
  
  public String getCommonRole()
  {
    return commonrole;
  }

  public boolean canUserChooseAccount()
  {
    return ischoiceoftheaccount;
  }

  public boolean isAuthenticationBoundToTheTcpConnection()
  {
    return isauthenticationboundtothetcpconnection;
  }
  
  public boolean mustCreateSession()
  {
    return mustcreatesession;
  }
  
  public boolean isOnlyKerberos()
  {
    return isonlykerberos;
  }
  
  public int getTimeOutNtlm()
  {
    return timeoutntlm;
  }
  
  public boolean canUserChooseBetwweenSpnegoAndNntlm()
  {
    return ischoicebetwweenspnegoandntlm;
  } 
}
